﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Properties;

namespace BorgExt
{
    public class Composer : RuntimeElement, ICreator, IComposer
    {
        internal static readonly Stack<Extension> ActivatorStack = new Stack<Extension>();

        public bool AutoOpen { get; set; }
        public bool AutoPlug { get; set; }
        public bool AutoSelect { get; set; }
        public bool AutoComposition { get; set; }

        public bool AllowComposition { get; internal set; }

        internal ExtensionObjectFactory ExtensionObjectFactory { get; set; }

        internal BehaviorCollection Behaviors { get; set; }

        private ExtensionBuilder ExtensionBuilder { get; set; }

        private InstanceStore.InstanceStoreModifier InstanceStoreModifier { get; set; }

        internal CompositionEventHandler<ExtensionEventArgs>[] CreatedEventHandlers { get; private set; }
        internal CompositionEventHandler<ExtensionEventArgs>[] DestroyedEventHandlers { get; private set; }

        internal CompositionEventHandler<ExtensionEventArgs>[] ActivatedEventHandlers { get; private set; }
        internal CompositionEventHandler<ExtensionEventArgs>[] DeactivatedEventHandlers { get; private set; }

        internal CompositionEventHandler<SlotEventArgs>[] OpenedEventHandlers { get; private set; }
        internal CompositionEventHandler<SlotEventArgs>[] ClosedEventHandlers { get; private set; }

        internal CompositionEventHandler<CompositionEventArgs>[] PluggedEventHandlers { get; private set; }
        internal CompositionEventHandler<CompositionEventArgs>[] UnpluggedEventHandlers { get; private set; }

        internal CompositionEventHandler<CompositionEventArgs>[] SelectedEventHandlers { get; private set; }
        internal CompositionEventHandler<CompositionEventArgs>[] DeselectedEventHandlers { get; private set; }

        private HashSet<Extension> IsCreatingSet { get; set; }
        private HashSet<Extension> IsActivatingSet { get; set; }
        private HashSet<Slot> IsOpeningSet { get; set; }

        private Dictionary<Slot, List<Plug>> IsPluggingDictionary { get; set; }
        private Dictionary<Slot, List<Plug>> IsSelectingDictionary { get; set; }

        private HashSet<Plug> IsPluggingSet { get; set; }
        private HashSet<Plug> IsSelectingSet { get; set; }

        private HashSet<Extension> IsDestroyingSet { get; set; }

        private Dictionary<Extension, object> IsDeactivatingDictionary { get; set; }

        private HashSet<Slot> IsClosingSet { get; set; }

        private Dictionary<Slot, List<Plug>> IsUnpluggingDictionary { get; set; }
        private Dictionary<Slot, List<Plug>> IsDeselectingDictionary { get; set; }

        public bool IsBusy { get; private set; }

        private Stack<HandlerInfo> OnDoneHandlers { get; set; }

        public event CompositionEventHandler<ExtensionEventArgs> ExtensionCreated
        {
            add
            {
                lock (LockObject)
                    CreatedEventHandlers = ArrayEx.Add(CreatedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    CreatedEventHandlers = ArrayEx.Remove(CreatedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<ExtensionEventArgs> ExtensionDestroyed
        {
            add
            {
                lock (LockObject)
                    DestroyedEventHandlers = ArrayEx.Add(DestroyedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    DestroyedEventHandlers = ArrayEx.Remove(DestroyedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<ExtensionEventArgs> ExtensionActivated
        {
            add
            {
                lock (LockObject)
                    ActivatedEventHandlers = ArrayEx.Add(ActivatedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    ActivatedEventHandlers = ArrayEx.Remove(ActivatedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<ExtensionEventArgs> ExtensionDeactivated
        {
            add
            {
                lock (LockObject)
                    DeactivatedEventHandlers = ArrayEx.Add(DeactivatedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    DeactivatedEventHandlers = ArrayEx.Remove(DeactivatedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<SlotEventArgs> SlotOpened
        {
            add
            {
                lock (LockObject)
                    OpenedEventHandlers = ArrayEx.Add(OpenedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    OpenedEventHandlers = ArrayEx.Remove(OpenedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<SlotEventArgs> SlotClosed
        {
            add
            {
                lock (LockObject)
                    ClosedEventHandlers = ArrayEx.Add(ClosedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    ClosedEventHandlers = ArrayEx.Remove(ClosedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<CompositionEventArgs> ExtensionPlugged
        {
            add
            {
                lock (LockObject)
                    PluggedEventHandlers = ArrayEx.Add(PluggedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    PluggedEventHandlers = ArrayEx.Remove(PluggedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<CompositionEventArgs> ExtensionUnplugged
        {
            add
            {
                lock (LockObject)
                    UnpluggedEventHandlers = ArrayEx.Add(UnpluggedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    UnpluggedEventHandlers = ArrayEx.Remove(UnpluggedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<CompositionEventArgs> ExtensionSelected
        {
            add
            {
                lock (LockObject)
                    SelectedEventHandlers = ArrayEx.Add(SelectedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    SelectedEventHandlers = ArrayEx.Remove(SelectedEventHandlers, value);
            }
        }

        public event CompositionEventHandler<CompositionEventArgs> ExtensionDeselected
        {
            add
            {
                lock (LockObject)
                    DeselectedEventHandlers = ArrayEx.Add(DeselectedEventHandlers, value);
            }
            remove
            {
                lock (LockObject)
                    DeselectedEventHandlers = ArrayEx.Remove(DeselectedEventHandlers, value);
            }
        }

        static Composer()
        { }

        protected internal Composer()
        { }

        protected override void Init()
        {
            OnDoneHandlers = new Stack<HandlerInfo>();
            AutoOpen = AutoPlug = AutoSelect = AutoComposition = AllowComposition = true;
            ExtensionBuilder = new ExtensionBuilder();
            
            if (ExtensionObjectFactory == null)
                ExtensionObjectFactory = ActivatorExtensionObjectFactory.CreateExtensionObject;
            
            InstanceStoreModifier = InstanceStore.Modifier;
            Behaviors = new BehaviorCollection(OnBehaviorAdded, OnBehaviorRemoved);
            
            IsCreatingSet = new HashSet<Extension>();
            IsActivatingSet = new HashSet<Extension>();
            IsOpeningSet = new HashSet<Slot>();
            
            IsPluggingDictionary = new Dictionary<Slot, List<Plug>>();
            IsSelectingDictionary = new Dictionary<Slot, List<Plug>>();
            
            IsPluggingSet = new HashSet<Plug>();
            IsSelectingSet = new HashSet<Plug>();
            IsDestroyingSet = new HashSet<Extension>();
            
            IsDeactivatingDictionary = new Dictionary<Extension, object>();
            
            IsClosingSet = new HashSet<Slot>();
            
            IsUnpluggingDictionary = new Dictionary<Slot, List<Plug>>();
            IsDeselectingDictionary = new Dictionary<Slot, List<Plug>>();
        }

        protected internal virtual void OnPluginAdded(Plugin plugin)
        {
            var wasBusy = BeginComposition();

            foreach (var plugType in plugin.ExtensionTypes.
                SelectMany(extensionType => (IEnumerable<PlugType>)extensionType.PlugTypes))
                InternalAutoCreateAndPlug(plugType);
            
            EndComposition(wasBusy);
        }

        protected internal virtual void OnPluginRemoved(Plugin plugin)
        {
            var wasBusy = BeginComposition();
            InternalDestroyAllExtensions(plugin);
            EndComposition(wasBusy);
        }

        public Extension Create(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanCreate(extensionType, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnCreatingExtension,
                            extensionType.Id,
                            extensionType.Name,
                            msg));
                }
                
                var compo = BeginComposition();
                var create = InternalCreate(extensionType);

                EndComposition(compo);

                return create;
            }
        }

        public Extension GetShared(ExtensionType extensionType)
        {
            if (extensionType == null)
                throw new ArgumentNullException("extensionType");

            lock (LockObject)
            {
                var ext = InstanceStore.InternalGetSharedExtension(extensionType);

                if (ext != null)
                    return ext;

                LogEventArgs args;

                if (!InternalCanCreate(extensionType, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnCreatingExtension,
                            extensionType.Id,
                            extensionType.Name,
                            msg));
                }
                
                var compo = BeginComposition();
                var create = InternalCreateShared(extensionType);
                
                EndComposition(compo);
                
                return create;
            }
        }

        public void Destroy(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanDestroy(extension, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnDestroyingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                if (InstanceStore.InternalIsDestroyed(extension))
                    return;

                var compo = BeginComposition();

                InternalDestroy(extension);
                EndComposition(compo);
            }
        }

        public object GetExtensionObject(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
            {
                if (InstanceStore.InternalIsActivated(extension))
                    return InstanceStore.InternalGetExtensionObject(extension);

                if (IsDeactivating(extension))
                    return IsDeactivatingDictionary[extension];

                LogEventArgs args;

                if (!InternalCanActivate(extension, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnActivatingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                var compo = BeginComposition();
                var acti = InternalActivate(extension);
                
                EndComposition(compo);
                
                return acti;
            }
        }

        public object Activate(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanActivate(extension, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnActivatingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                bool compo = BeginComposition();
                object acti = InstanceStore.InternalIsActivated(extension)
                    ? InstanceStore.InternalGetExtensionObject(extension)
                    : InternalActivate(extension);
                
                EndComposition(compo);
                
                return acti;
            }
        }

        public void Deactivate(Extension extension)
        {
            if (extension == null)
                throw new ArgumentNullException("extension");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanDeactivate(extension, true, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnDeactivatingExtension,
                            extension.Id,
                            extension.Name,
                            msg));
                }
                
                if (!InstanceStore.InternalIsActivated(extension))
                    return;
                
                var compo = BeginComposition();
                
                InternalDeactivate(extension);
                EndComposition(compo);
            }
        }

        public void Open(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanOpen(slot, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnOpeningSlot,
                            slot.Id,
                            slot.Name,
                            msg));
                }
                
                var compo = BeginComposition();
                
                if (!InstanceStore.InternalIsActivated(slot.Extension))
                    InternalActivate(slot.Extension);
                
                if (!InstanceStore.InternalIsOpen(slot))
                    InternalOpen(slot);
                
                EndComposition(compo);
            }
        }

        public void Plug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanPlug(slot, plug, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                            Messages.Composer_ErrorOnPluggingExtension,
                            (object) plug.Extension.Id,
                            (object) plug.Extension.Name,
                            (object) slot.Extension.Id,
                            (object) slot.Extension.Name,
                            (object) slot.Name,
                            (object) msg));
                }
                
                if (InstanceStore.InternalIsPlugged(slot, plug))
                    return;

                var compo = BeginComposition();
                
                InternalPlug(slot, plug);
                EndComposition(compo);
            }
        }

        public void Select(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanSelect(slot, plug, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                        Messages.Composer_ErrorOnSelectingExtension,
                        (object)plug.Extension.Id,
                        (object)plug.Extension.Name,
                        (object)slot.Extension.Id,
                        (object)slot.Extension.Name,
                        (object)slot.Name, (object)msg));
                }
                
                if (InstanceStore.InternalIsSelected(slot, plug))
                    return;

                var compo = BeginComposition();
                
                InternalSelect(slot, plug);
                EndComposition(compo);
            }
        }

        public void Close(Slot slot)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanClose(slot, true, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                        Messages.Composer_ErrorOnClosingSlot,
                        slot.Id,
                        slot.Name,
                        msg));
                }
                
                if (!InstanceStore.InternalIsOpen(slot))
                    return;
                
                var compo = BeginComposition();
                
                InternalClose(slot);
                EndComposition(compo);
            }
        }

        public void Unplug(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanUnplug(slot, plug, true, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                        Messages.Composer_ErrorOnUnpluggingExtension,
                        (object)plug.Extension.Id,
                        (object)plug.Extension.Name,
                        (object)slot.Extension.Id,
                        (object)slot.Extension.Name,
                        (object)slot.Name,
                        (object)msg));
                }
                
                if (!InstanceStore.InternalIsPlugged(slot, plug))
                    return;
                
                var compo = BeginComposition();
                
                InternalUnplug(slot, plug);
                EndComposition(compo);
            }
        }

        public void Deselect(Slot slot, Plug plug)
        {
            if (slot == null)
                throw new ArgumentNullException("slot");

            if (plug == null)
                throw new ArgumentNullException("plug");

            lock (LockObject)
            {
                LogEventArgs args;

                if (!InternalCanDeselect(slot, plug, true, out args))
                {
                    var msg = args != null
                        ? args.Message
                        : string.Empty;

                    throw new InvalidOperationException(
                        string.Format(
                        Messages.Composer_ErrorOnDeselectingExtension,
                        (object)plug.Extension.Id,
                        (object)plug.Extension.Name,
                        (object)slot.Extension.Id,
                        (object)slot.Extension.Name,
                        (object)slot.Name,
                        (object)msg));
                }
                
                if (!InstanceStore.InternalIsSelected(slot, plug))
                    return;
                
                var compo = BeginComposition();
                
                InternalDeselect(slot, plug);
                EndComposition(compo);
            }
        }

        public void InvokeOnCompositionDone(NoParameterHandler handler)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            lock (LockObject)
            {
                OnDoneHandlers.Push(new NoParameterHandlerInfo(handler));
                
                if (IsBusy)
                    return;
                
                InvokeOnDoneHandlers();
            }
        }

        public void InvokeOnCompositionDone<T>(GenericParameterHandler<T> handler, T args)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            lock (LockObject)
            {
                OnDoneHandlers.Push(new GenericParameterHandlerInfo<T>(handler, args));
                
                if (IsBusy)
                    return;
                
                InvokeOnDoneHandlers();
            }
        }

        public void InvokeOnCompositionDone(Delegate @delegate, params object[] args)
        {
            if (@delegate == null)
                throw new ArgumentNullException("delegate");

            lock (LockObject)
            {
                OnDoneHandlers.Push(new DelegateHandlerInfo(@delegate, args));
                
                if (IsBusy)
                    return;
                
                InvokeOnDoneHandlers();
            }
        }

        private Extension InternalCreate(ExtensionType extensionType)
        {
            var extension = ExtensionBuilder.CreateExtension(extensionType, Qualifier);
            
            AddCreating(extension);
            InstanceStoreModifier.InternalAddExtension(extension);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionCreated,
                new object[]
                    {
                        extension.Id,
                        extension.Name
                    });

            Notifier.OnCreated(new ExtensionEventArgs(extension));
            RemoveCreating(extension);
            
            return extension;
        }

        private Extension InternalCreateShared(ExtensionType extensionType)
        {
            var extension = InternalCreate(extensionType);
            InstanceStoreModifier.InternalSetSharedExtension(extension);
            return extension;
        }

        private object InternalActivate(Extension extension)
        {
            Debug.Assert(!InstanceStore.InternalIsActivated(extension));

            try
            {
                var args = new ExtensionEventArgs(extension);
                
                AddActivating(extension);
                Notifier.OnActivating(args);
                ActivatorStack.Push(extension);
                
                var asm = extension.AssemblyElement.Assembly;
                var extensionType = extension.ExtensionType;
                
                if (asm.ReflectionOnly)
                {
                    var domain = AppDomain.CurrentDomain.Load(asm.GetName());
                    Qualifier.UpdateAssemblyReference(extensionType.Plugin, domain);
                }

                var extensionObject = ExtensionObjectFactory(extension, extensionType);
                
                AddReflectionEventHandlers(extension, extensionObject);
                InstanceStoreModifier.InternalAddActivated(extension, extensionObject);

                Logger.OnLog(
                    VerbosityLevel.Detailed,
                    this,
                    Messages.Composer_ExtensionActivated,
                    new object[]
                        {
                            extension.Id,
                            extension.Name
                        });

                InternalAutoOpenSlots(extension);
                Notifier.OnActivated(args);
            }
            finally
            {
                ActivatorStack.Pop();
                RemoveActivating(extension);
            }

            Debug.Assert(InstanceStore.InternalIsActivated(extension));

            return extension.extensionObject;
        }

        private void InternalOpen(Slot slot)
        {
            Debug.Assert(InstanceStore.InternalIsActivated(slot.Extension));
            Debug.Assert(!InstanceStore.InternalIsOpen(slot));

            var args = new SlotEventArgs(slot);
            
            AddOpening(slot);
            Notifier.OnOpening(args);
            InstanceStoreModifier.InternalAddOpened(slot);

            Logger.OnLog(
                VerbosityLevel.Diagnostic,
                this,
                Messages.Composer_SlotOpened,
                (object) slot.Id,
                (object) slot.Name,
                (object) slot.Extension.Name);
            
            Notifier.OnOpened(args);
            RemoveOpening(slot);
            Debug.Assert(InstanceStore.InternalIsOpen(slot));
            InternalAutoCreateAndPlug(slot);
        }

        private void InternalPlug(Slot slot, Plug plug)
        {
            Debug.Assert(InstanceStore.InternalIsOpen(slot));
            Debug.Assert(!InstanceStore.InternalIsPlugged(slot, plug));
            AddPlugging(slot, plug);

            var args = new CompositionEventArgs(slot, plug);

            Notifier.OnPlugging(args);
            InstanceStoreModifier.InternalAddPlugged(slot, plug);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionPlugged,
                (object) plug.Extension.Id,
                (object) plug.Extension.Name,
                (object) slot.Extension.Id,
                (object) slot.Extension.Name,
                (object) slot.Name);
            
            Notifier.OnPlugged(args);
            RemovePlugging(slot, plug);
            Debug.Assert(InstanceStore.InternalIsPlugged(slot, plug));
            InternalAutoSelect(slot, plug);
        }

        private void InternalSelect(Slot slot, Plug plug)
        {
            Debug.Assert(InstanceStore.InternalIsPlugged(slot, plug));
            Debug.Assert(!InstanceStore.InternalIsSelected(slot, plug));
            AddSelecting(slot, plug);
            
            var args = new CompositionEventArgs(slot, plug);
            
            Notifier.OnSelecting(args);
            InstanceStoreModifier.InternalAddSelected(slot, plug);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionSelected,
                (object) plug.Extension.Id,
                (object) plug.Extension.Name,
                (object) slot.Extension.Id,
                (object) slot.Extension.Name,
                (object) slot.Name);
            
            Notifier.OnSelected(args);
            RemoveSelecting(slot, plug);
            Debug.Assert(InstanceStore.InternalIsSelected(slot, plug));
        }

        private void InternalDestroy(Extension extension)
        {
            Debug.Assert(!InstanceStore.InternalIsDestroyed(extension));
            AddDestroying(extension);
            
            if (!IsDeactivating(extension) && InstanceStore.InternalIsActivated(extension))
                InternalDeactivate(extension);

            foreach (var plug in extension.Plugs)
                InternalUnplugAll(plug);

            InstanceStoreModifier.InternalRemoveExtension(extension);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionDestroyed,
                new object[]
                    {
                        extension.Id,
                        extension.Name
                    });

            Notifier.OnDestroyed(new ExtensionEventArgs(extension));
            RemoveDestroying(extension);
            Debug.Assert(InstanceStore.InternalIsDestroyed(extension));
        }

        private void InternalDeactivate(Extension extension)
        {
            Debug.Assert(InstanceStore.IsActivated(extension));
            AddDeactivating(extension, extension.Object);
            var args = new ExtensionEventArgs(extension);
            Notifier.OnDeactivating(args);
            InternalCloseAllSlots(extension);

            var disposable = extension.Object as IDisposable;

            if (disposable != null)
                disposable.Dispose();

            RemoveReflectionHandlers(extension);
            InstanceStoreModifier.InternalRemoveActivated(extension);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionDeactivated,
                new object[]
                    {
                        extension.Id,
                        extension.Name
                    });

            Notifier.OnDeactivated(args);
            RemoveDeactivating(extension);
            Debug.Assert(!InstanceStore.IsActivated(extension));
        }

        private void InternalClose(Slot slot)
        {
            Debug.Assert(InstanceStore.InternalIsOpen(slot));
            AddClosing(slot);

            var args = new SlotEventArgs(slot);

            Notifier.OnClosing(args);
            InternalUnplugAll(slot);
            InstanceStoreModifier.InternalRemoveOpened(slot);

            Logger.OnLog(
                VerbosityLevel.Diagnostic,
                this,
                Messages.Composer_SlotClosed,
                (object) slot.Id,
                (object) slot.Name,
                (object) slot.Extension.Name);
            
            Notifier.OnClosed(args);
            RemoveClosing(slot);
            Debug.Assert(!InstanceStore.InternalIsOpen(slot));
        }

        private void InternalUnplug(Slot slot, Plug plug)
        {
            Debug.Assert(InstanceStore.InternalIsPlugged(slot, plug));
            AddUnplugging(slot, plug);
            
            var args = new CompositionEventArgs(slot, plug);
            
            Notifier.OnUnplugging(args);
            
            if (InstanceStore.InternalIsSelected(slot, plug) && !IsDeselecting(slot, plug))
                InternalDeselect(slot, plug);

            InstanceStoreModifier.InternalRemovePlugged(slot, plug);

            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionUnplugged,
                (object) plug.Extension.Id,
                (object) plug.Extension.Name,
                (object) slot.Extension.Id,
                (object) slot.Extension.Name,
                (object) slot.Name);
            
            Notifier.OnUnplugged(args);
            RemoveUnplugging(slot, plug);
            Debug.Assert(!InstanceStore.InternalIsPlugged(slot, plug));

            if (HasPluggedPlugs(plug.Extension) ||
                IsDestroying(plug.Extension) ||
                InstanceStore.InternalIsDestroyed(plug.Extension))
                return;
            
            InternalDestroy(plug.Extension);
        }

        private void InternalDeselect(Slot slot, Plug plug)
        {
            Debug.Assert(InstanceStore.InternalIsSelected(slot, plug));
            AddDeselecting(slot, plug);
            
            var args = new CompositionEventArgs(slot, plug);
            
            Notifier.OnDeselecting(args);
            InstanceStoreModifier.InternalRemoveSelected(slot, plug);
            
            Logger.OnLog(
                VerbosityLevel.Detailed,
                this,
                Messages.Composer_ExtensionDeselected,
                (object)plug.Extension.Id,
                (object)plug.Extension.Name,
                (object)slot.Extension.Id,
                (object)slot.Extension.Name,
                (object)slot.Name);
            
            Notifier.OnDeselected(args);
            RemoveDeselecting(slot, plug);
            Debug.Assert(!InstanceStore.InternalIsSelected(slot, plug));
        }

        private bool BeginComposition()
        {
            if (IsBusy)
                return true;

            IsBusy = true;

            return false;
        }

        private void EndComposition(bool wasBusy)
        {
            if (wasBusy)
                return;
            
            IsBusy = false;
            InvokeOnDoneHandlers();
        }

        private void InvokeOnDoneHandlers()
        {
            while (OnDoneHandlers.Count > 0)
                OnDoneHandlers.Pop().Execute();
        }

        private void OnBehaviorAdded(IBehavior behavior)
        {
            behavior.Bind(this);
        }

        private void OnBehaviorRemoved(IBehavior behavior)
        {
            behavior.Unbind(this);
        }

        private void InternalAutoOpenSlots(Extension extension)
        {
            foreach (var slot in extension.Slots.
                Where(slot => slot.AutoOpen).
                TakeWhile(slot => AutoComposition && AutoOpen && AllowComposition).
                Where(slot => !InstanceStore.InternalIsOpen(slot)))
            {
                LogEventArgs log;

                if (!InternalCanOpen(slot, out log))
                {
                    string logMsg;
                    VerbosityLevel verbosityLevel;

                    if (log != null)
                    {
                        logMsg = log.Message;
                        verbosityLevel = GetVerbosityLevel(log);
                    }
                    else
                    {
                        logMsg = string.Empty;
                        verbosityLevel = VerbosityLevel.Diagnostic;
                    }

                    Logger.OnLog(
                        verbosityLevel,
                        this,
                        string.Format(
                            Messages.Composer_OpeningSlotCanceled,
                            slot.Id,
                            slot.Name,
                            logMsg),
                        new object[0]);
                }
                else
                    InternalOpen(slot);
            }
        }

        private void InternalAutoCreateAndPlug(PlugType plugType)
        {
            foreach (var slot in InstanceStore.InternalGetSlots(plugType.Name).
                Where(slot => InstanceStore.InternalIsOpen(slot) && slot.AutoPlug).
                TakeWhile(slot => AutoComposition && AutoPlug && AllowComposition))
                InternalAutoCreateAndPlug(slot, plugType);
        }

        private void InternalAutoCreateAndPlug(Slot slot)
        {
            foreach (var plugType in TypeStore.InternalGetPlugTypes(slot.Name).
                TakeWhile(plugType => AutoComposition && InstanceStore.InternalIsOpen(slot) &&
                                      (slot.AutoPlug && AutoPlug) && AllowComposition))
                InternalAutoCreateAndPlug(slot, plugType);
        }

        private void InternalAutoCreateAndPlug(Slot slot, PlugType plugType)
        {
            Extension extension;
            Plug plug;
            bool creatable;
            CompositionEventArgs args;
            LogEventArgs log;
            bool composable;

            if (InstanceStore.InternalGetSharedExtension(plugType.ExtensionType) != null && slot.Shared)
            {
                extension = InstanceStore.InternalGetSharedExtension(plugType.ExtensionType);
                plug = extension.Plugs[plugType.Name];

                if (!plug.AutoPlug)
                    return;

                creatable = false;
                args = new CompositionEventArgs(slot, plug);
                composable = !InternalCanComposeContributor(args, out log);
            }
            else
            {
                if (!plugType.AutoPlug || !Qualifier.InternalQualify(plugType))
                    return;

                creatable = true;

                extension = slot.Shared
                    ? InternalCreateShared(plugType.ExtensionType)
                    : InternalCreate(plugType.ExtensionType);

                plug = extension.Plugs[plugType.Name];
                args = new CompositionEventArgs(slot, plug);
                composable = false;
                log = null;
            }

            if (composable || !Notifier.CanPlug(args, out log))
            {
                string logMsg;
                VerbosityLevel verbosityLevel;

                if (log != null)
                {
                    logMsg = log.Message;
                    verbosityLevel = GetVerbosityLevel(log);
                }
                else
                {
                    logMsg = string.Empty;
                    verbosityLevel = VerbosityLevel.Diagnostic;
                }

                Logger.OnLog(
                    verbosityLevel,
                    this,
                    string.Format(
                        Messages.Composer_PluggingExtensionCanceled,
                        (object) plug.Extension.Id,
                        (object) plug.Extension.Name,
                        (object) slot.Extension.Id,
                        (object) slot.Extension.Name,
                        (object) slot.Name,
                        (object) logMsg),
                    new object[0]);
                
                if (!creatable)
                    return;

                Debug.Assert(!InstanceStore.InternalIsDestroyed(extension));
                InternalDestroy(extension);
            }
            else if (!InstanceStore.InternalIsPlugged(slot, plug))
                InternalPlug(slot, args.Plug);
        }

        private void InternalAutoSelect(Slot slot, Plug plug)
        {
            if (!AutoComposition ||
                InstanceStore.InternalIsSelected(slot, plug) ||
                (!slot.AutoSelect ||
                !AutoSelect) ||
                !AllowComposition)
                return;
            
            InternalSelect(slot, plug);
        }

        private static VerbosityLevel GetVerbosityLevel(LogEventArgs log)
        {
            VerbosityLevel verbosityLevel;
            
            switch (log.Type)
            {
                case LogType.Info:
                    verbosityLevel = VerbosityLevel.Diagnostic;
                    break;
                
                case LogType.Warning:
                    verbosityLevel = VerbosityLevel.Detailed;
                    break;
                
                case LogType.Error:
                    verbosityLevel = VerbosityLevel.Normal;
                    break;
                
                default:
                    verbosityLevel = VerbosityLevel.Diagnostic;
                    break;
            }

            return verbosityLevel;
        }

        private void InternalDestroyAllExtensions(Plugin plugin)
        {
            foreach (var extension in plugin.ExtensionTypes.
                SelectMany(extensionType =>
                    (IEnumerable<Extension>) InstanceStore.InternalGetExtensions(extensionType)).
                Where(extension =>
                    !InstanceStore.InternalIsDestroyed(extension) && !IsDestroying(extension)))
                InternalDestroy(extension);
        }

        private void InternalCloseAllSlots(Extension extension)
        {
            foreach (var slot in extension.Slots.
                Where(slot => InstanceStore.InternalIsOpen(slot) && !IsClosing(slot)))
                InternalClose(slot);
        }

        private void InternalUnplugAll(Plug plug)
        {
            foreach (var slot in InstanceStore.InternalGetSlotsWherePlugged(plug).
                Where(slot => InstanceStore.InternalIsPlugged(slot, plug) && !IsUnplugging(slot, plug)))
                InternalUnplug(slot, plug);
        }

        private void InternalUnplugAll(Slot slot)
        {
            foreach (var plug in InstanceStore.InternalGetPluggedPlugs(slot).
                Where(plug => InstanceStore.InternalIsPlugged(slot, plug) && !IsUnplugging(slot, plug)))
                InternalUnplug(slot, plug);
        }

        private bool InternalCanCreate(ExtensionType extensionType, out LogEventArgs log)
        {
            if (!AllowComposition)
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                return false;
            }
            
            if (!TypeStore.InternalGetExtensionTypes(extensionType.Name).Contains(extensionType))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionTypeIsNotInTypeStore);
                return false;
            }
            
            if (!Qualifier.InternalWeakQualify(extensionType))
            {
                log = new LogEventArgs(LogType.Error, string.Format(
                    Messages.Qualifier_ElementIsNotQualified, "Extension type"));
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanActivate(Extension extension, out LogEventArgs log)
        {
            if (!AllowComposition)
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                return false;
            }
            
            if (InstanceStore.InternalIsDestroyed(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsDestroyed);
                return false;
            }
            
            if (IsDestroying(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsDestroying);
                return false;
            }
            
            if (IsDeactivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsDeactivating);
                return false;
            }
            
            if (!Notifier.CanActivate(new ExtensionEventArgs(extension), out log))
                return false;

            if (!InstanceStore.InternalIsActivated(extension) && IsActivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }
            
            log = null;
            return true;
        }

        private bool InternalCanOpen(Slot slot, out LogEventArgs log)
        {
            if (InstanceStore.InternalIsActivated(slot.Extension))
            {
                if (!InternalCanComposeHost(slot, out log))
                    return false;
            }
            else if (!InternalCanActivate(slot.Extension, out log))
                return false;

            if (!Qualifier.InternalWeakQualify(slot.SlotType))
            {
                log = new LogEventArgs(LogType.Error, string.Format(
                    Messages.Qualifier_ElementIsNotQualified, "Slot type"));
                return false;
            }
            
            if (!Notifier.CanOpen(new SlotEventArgs(slot), out log))
                return false;

            if (!InstanceStore.InternalIsOpen(slot) && IsOpening(slot))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanPlug(Slot slot, Plug plug, out LogEventArgs log)
        {
            if (InstanceStore.InternalIsOpen(slot))
            {
                if (!InternalCanComposeHost(slot, out log))
                    return false;
            }
            else if (!InternalCanOpen(slot, out log))
                return false;

            if (slot.Name != plug.Name)
            {
                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                        Messages.Composer_Error_PlugIsNotCompatibleToSlot,
                        (object) plug.Id,
                        (object) plug.Name,
                        (object) slot.Id,
                        (object) slot.Name));
                return false;
            }

            if (!Qualifier.InternalWeakQualify(plug.PlugType))
            {
                log = new LogEventArgs(LogType.Error, string.Format(
                    Messages.Qualifier_ElementIsNotQualified, "Plug type"));
                return false;
            }
            
            var args = new CompositionEventArgs(slot, plug);

            if (!InternalCanComposeContributor(args, out log) || !Notifier.CanPlug(args, out log))
                return false;

            if (!InstanceStore.InternalIsPlugged(slot, plug) && IsPlugging(slot, plug))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanSelect(Slot slot, Plug plug, out LogEventArgs log)
        {
            if (InstanceStore.InternalIsSelected(slot, plug))
            {
                if (!InternalCanComposeHost(slot, out log))
                    return false;
            }
            else if (!InternalCanPlug(slot, plug, out log))
                return false;

            if (IsDeselecting(slot, plug))
            {
                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                        Messages.Composer_Error_SlotIsDeselectingThePlug,
                        (object) slot.Id,
                        (object) slot.Name,
                        (object) plug.Id,
                        (object) plug.Name));
                return false;
            }
            
            var args = new CompositionEventArgs(slot, plug);
            
            if (!InternalCanComposeContributor(args, out log) || !Notifier.CanSelect(args, out log))
                return false;

            if (!InstanceStore.InternalIsSelected(slot, plug) && IsSelecting(slot, plug))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanComposeHost(Slot slot, out LogEventArgs log)
        {
            if (!AllowComposition)
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                return false;
            }
            
            var extension = slot.Extension;

            if (IsDestroying(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_HostExtensionIsDestroying);
                return false;
            }
            
            if (IsDeactivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_HostExtensionIsDeactivating);
                return false;
            }
            
            if (IsClosing(slot))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_SlotIsClosing);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanComposeContributor(CompositionEventArgs args, out LogEventArgs log)
        {
            var slot = args.Slot;
            var plug = args.Plug;

            if (IsUnplugging(slot, plug))
            {
                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                        Messages.Composer_Error_SlotIsUnpluggingThePlug,
                        (object) slot.Id,
                        (object) slot.Name,
                        (object) plug.Id,
                        (object) plug.Name));
                return false;
            }
            
            var extension = args.Plug.Extension;

            if (IsDeactivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ContributorExtensionIsDeactivating);
                return false;
            }
            
            if (IsDestroying(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ContributorExtensionIsDestroying);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanDestroy(Extension extension, out LogEventArgs log)
        {
            if (!InstanceStore.InternalIsActivated(extension))
            {
                if (!AllowComposition)
                {
                    log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                    return false;
                }
                
                if (IsActivating(extension))
                {
                    log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsActivating);
                    return false;
                }
            }
            else if (!InternalCanDeactivate(extension, false, out log))
                return false;

            if (InternalIsCreating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsCreating);
                return false;
            }
            
            if (!InstanceStore.InternalIsDestroyed(extension) && IsDestroying(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanDeactivate(Extension extension, bool notifyCanHandler, out LogEventArgs log)
        {
            if (!AllowComposition)
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                return false;
            }
            
            foreach (var slot in extension.Slots)
            {
                if (InstanceStore.InternalIsOpen(slot))
                {
                    if (!InternalCanClose(slot, false, out log))
                        return false;
                }
                else if (IsOpening(slot))
                {
                    log = new LogEventArgs(LogType.Error, Messages.Composer_Error_SlotIsOpening);
                    return false;
                }
            }

            foreach (var plug in extension.Plugs)
            {
                Slot composingSlot;

                if (IsPlugging(plug))
                {
                    composingSlot = GetComposingSlot(plug, IsPluggingDictionary);

                    log = new LogEventArgs(
                        LogType.Error,
                        string.Format(
                            Messages.Composer_Error_SlotIsPluggingThePlug,
                            (object) composingSlot.Id,
                            (object) composingSlot.Name,
                            (object) plug.Id,
                            (object) plug.Name));
                    
                    return false;
                }

                if (!IsSelecting(plug))
                    continue;

                composingSlot = GetComposingSlot(plug, IsSelectingDictionary);

                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                        Messages.Composer_Error_SlotIsSelectingThePlug,
                        (object) composingSlot.Id,
                        (object) composingSlot.Name,
                        (object) plug.Id, (object) plug.Name));
                    
                return false;
            }
            
            if (IsActivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_ExtensionIsActivating);
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanDeactivate(new ExtensionEventArgs(extension), out log))
                return false;

            if (InstanceStore.InternalIsActivated(extension) && IsDeactivating(extension))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private static Slot GetComposingSlot(Plug plug, Dictionary<Slot, List<Plug>> isComposingDictionary)
        {
            return (from keyValuePair in isComposingDictionary
                    where keyValuePair.Value.Contains(plug)
                    select keyValuePair.Key).FirstOrDefault();
        }

        private bool InternalCanClose(Slot slot, bool notifyCanHandler, out LogEventArgs log)
        {
            if (InstanceStore.InternalGetPluggedPlugs(slot).Count == 0)
            {
                if (!AllowComposition)
                {
                    log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                    return false;
                }
                
                if (IsPlugging(slot))
                {
                    var plug = IsPluggingDictionary[slot][0];

                    log = new LogEventArgs(
                        LogType.Error,
                        string.Format(
                            Messages.Composer_Error_SlotIsPluggingThePlug,
                            (object) slot.Id,
                            (object) slot.Name,
                            (object) plug.Id,
                            (object) plug.Name));
                    
                    return false;
                }
            }
            else
            {
                foreach (var plug in InstanceStore.InternalGetPluggedPlugs(slot))
                    if (!InternalCanUnplug(slot, plug, false, out log))
                        return false;
            }

            if (IsOpening(slot))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_SlotIsOpening);
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanClose(new SlotEventArgs(slot), out log))
                return false;

            if (InstanceStore.InternalIsOpen(slot) && IsClosing(slot))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanUnplug(Slot slot, Plug plug, bool notifyCanHandler, out LogEventArgs log)
        {
            if (!InstanceStore.InternalIsSelected(slot, plug))
            {
                if (!AllowComposition)
                {
                    log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                    return false;
                }

                if (IsSelecting(slot, plug))
                {
                    log = new LogEventArgs(
                        LogType.Error,
                        string.Format(
                            Messages.Composer_Error_SlotIsSelectingThePlug,
                            (object) slot.Id,
                            (object) slot.Name,
                            (object) plug.Id,
                            (object) plug.Name));
                    
                    return false;
                }
            }
            else if (!InternalCanDeselect(slot, plug, false, out log))
                return false;

            if (IsPlugging(slot, plug))
            {
                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                    Messages.Composer_Error_SlotIsPluggingThePlug,
                    (object)slot.Id,
                    (object)slot.Name,
                    (object)plug.Id,
                    (object)plug.Name));
                
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanUnplug(new CompositionEventArgs(slot, plug), out log))
                return false;

            if (InstanceStore.InternalIsPlugged(slot, plug) && IsUnplugging(slot, plug))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private bool InternalCanDeselect(Slot slot, Plug plug, bool notifyCanHandler, out LogEventArgs log)
        {
            if (!AllowComposition)
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_CompositionIsNotAllowed);
                return false;
            }
            
            if (IsSelecting(slot, plug))
            {
                log = new LogEventArgs(
                    LogType.Error,
                    string.Format(
                        Messages.Composer_Error_SlotIsSelectingThePlug,
                        (object) slot.Id,
                        (object) slot.Name,
                        (object) plug.Id,
                        (object) plug.Name));
                
                return false;
            }
            
            if (notifyCanHandler && !Notifier.CanDeselect(new CompositionEventArgs(slot, plug), out log))
                return false;

            if (InstanceStore.InternalIsSelected(slot, plug) && IsDeselecting(slot, plug))
            {
                log = new LogEventArgs(LogType.Error, Messages.Composer_Error_RecursiveCallIsNotAllowed);
                return false;
            }

            log = null;
            return true;
        }

        private void AddCreating(Extension extension)
        {
            IsCreatingSet.Add(extension);
        }

        private void RemoveCreating(Extension extension)
        {
            IsCreatingSet.Remove(extension);
        }

        private bool InternalIsCreating(Extension extension)
        {
            return IsCreatingSet.Contains(extension);
        }

        private void AddActivating(Extension extension)
        {
            IsActivatingSet.Add(extension);
        }

        private void RemoveActivating(Extension extension)
        {
            IsActivatingSet.Remove(extension);
        }

        private bool IsActivating(Extension extension)
        {
            return IsActivatingSet.Contains(extension);
        }

        private void AddOpening(Slot slot)
        {
            IsOpeningSet.Add(slot);
        }

        private void RemoveOpening(Slot slot)
        {
            IsOpeningSet.Remove(slot);
        }

        private bool IsOpening(Slot slot)
        {
            return IsOpeningSet.Contains(slot);
        }

        private void AddPlugging(Slot slot, Plug plug)
        {
            (IsPluggingDictionary.ContainsKey(slot)
                ? IsPluggingDictionary[slot]
                : (IsPluggingDictionary[slot] = new List<Plug>(1))).Add(plug);

            IsPluggingSet.Add(plug);
        }

        private void RemovePlugging(Slot slot, Plug plug)
        {
            var plugs = IsPluggingDictionary[slot];

            if (plugs.Count == 1)
                IsPluggingDictionary.Remove(slot);
            else
                plugs.Remove(plug);
            
            IsPluggingSet.Remove(plug);
        }

        private bool IsPlugging(Slot slot, Plug plug)
        {
            return
                IsPluggingDictionary.ContainsKey(slot) &&
                IsPluggingDictionary[slot].Contains(plug);
        }

        private bool IsPlugging(Slot slot)
        {
            return IsPluggingDictionary.ContainsKey(slot);
        }

        private bool IsPlugging(Plug plug)
        {
            return IsPluggingSet.Contains(plug);
        }

        private void AddSelecting(Slot slot, Plug plug)
        {
            (IsSelectingDictionary.ContainsKey(slot)
                ? IsSelectingDictionary[slot]
                : (IsSelectingDictionary[slot] = new List<Plug>(1))).Add(plug);
            
            IsSelectingSet.Add(plug);
        }

        private void RemoveSelecting(Slot slot, Plug plug)
        {
            var plugs = IsSelectingDictionary[slot];

            if (plugs.Count == 1)
                IsSelectingDictionary.Remove(slot);
            else
                plugs.Remove(plug);
            
            IsSelectingSet.Remove(plug);
        }

        private bool IsSelecting(Slot slot, Plug plug)
        {
            return
                IsSelectingDictionary.ContainsKey(slot) &&
                IsSelectingDictionary[slot].Contains(plug);
        }

        private bool IsSelecting(Plug plug)
        {
            return IsSelectingSet.Contains(plug);
        }

        private void AddDestroying(Extension extension)
        {
            IsDestroyingSet.Add(extension);
        }

        private void RemoveDestroying(Extension extension)
        {
            IsDestroyingSet.Remove(extension);
        }

        private bool IsDestroying(Extension extension)
        {
            return IsDestroyingSet.Contains(extension);
        }

        private void AddDeactivating(Extension extension, object extensionObject)
        {
            IsDeactivatingDictionary.Add(extension, extensionObject);
        }

        private void RemoveDeactivating(Extension extension)
        {
            IsDeactivatingDictionary.Remove(extension);
        }

        private bool IsDeactivating(Extension extension)
        {
            return IsDeactivatingDictionary.ContainsKey(extension);
        }

        private void AddClosing(Slot slot)
        {
            IsClosingSet.Add(slot);
        }

        private void RemoveClosing(Slot slot)
        {
            IsClosingSet.Remove(slot);
        }

        private bool IsClosing(Slot slot)
        {
            return IsClosingSet.Contains(slot);
        }

        private void AddUnplugging(Slot slot, Plug plug)
        {
            (IsUnpluggingDictionary.ContainsKey(slot)
                ? IsUnpluggingDictionary[slot]
                : (IsUnpluggingDictionary[slot] = new List<Plug>(1))).Add(plug);
        }

        private void RemoveUnplugging(Slot slot, Plug plug)
        {
            var plugs = IsUnpluggingDictionary[slot];

            if (plugs.Count == 1)
                IsUnpluggingDictionary.Remove(slot);
            else
                plugs.Remove(plug);
        }

        private bool IsUnplugging(Slot slot, Plug plug)
        {
            return
                IsUnpluggingDictionary.ContainsKey(slot) &&
                IsUnpluggingDictionary[slot].Contains(plug);
        }

        private void AddDeselecting(Slot slot, Plug plug)
        {
            (IsDeselectingDictionary.ContainsKey(slot)
                ? IsDeselectingDictionary[slot]
                : (IsDeselectingDictionary[slot] = new List<Plug>(1))).Add(plug);
        }

        private void RemoveDeselecting(Slot slot, Plug plug)
        {
            var plugs = IsDeselectingDictionary[slot];

            if (plugs.Count == 1)
                IsDeselectingDictionary.Remove(slot);
            else
                plugs.Remove(plug);
        }

        private bool IsDeselecting(Slot slot, Plug plug)
        {
            return
                IsDeselectingDictionary.ContainsKey(slot) &&
                IsDeselectingDictionary[slot].Contains(plug);
        }

        private bool HasPluggedPlugs(Extension extension)
        {
            return extension.Plugs.Any(pluggedPlug =>
                InstanceStore.InternalGetSlotsWherePlugged(pluggedPlug).Count > 0);
        }

        private static T[] ReplaceReflectionHandler<T>(T[] eventHandlers, T eventHandler)
        {
            eventHandlers[0] = eventHandler;
            return eventHandlers;
        }

        private static T[] InsertReflectionHandler<T>(T[] eventHandlers, T eventHandler)
        {
            return ArrayEx.Insert(eventHandlers, 0, eventHandler);
        }

        private void AddReflectionEventHandlers(Extension extension, object extensionObject)
        {
            AddExtensionReflectionHandlers(extension, extensionObject);
            
            foreach (var plug in extension.Plugs.Where(plug =>
                Qualifier.InternalIsQualified(plug.PlugType)))
                AddPlugReflectionHandlers(plug, extensionObject);
            
            foreach (var slot in extension.Slots.Where(slot =>
                Qualifier.InternalIsQualified(slot.SlotType)))
                AddSlotReflectionHandlers(slot, extensionObject);
        }

        internal static void AddExtensionReflectionHandlers(Extension extension, object extensionObject)
        {
            var reflectionHandler = !extension.reflectionHandlersAdded
                ? new AddReflectionHandler<CompositionEventHandler<ExtensionEventArgs>>(
                    InsertReflectionHandler<CompositionEventHandler<ExtensionEventArgs>>)
                : new AddReflectionHandler<CompositionEventHandler<ExtensionEventArgs>>(
                    ReplaceReflectionHandler<CompositionEventHandler<ExtensionEventArgs>>);
            
            extension.reflectionHandlersAdded = true;
            var extensionType = extension.ExtensionType;
            
            if (extensionType.OnCreated != null && extensionType.OnCreated.MethodInfo != null)
                extension.ActivatedEventHandlers = reflectionHandler(extension.ActivatedEventHandlers,
                    new ReflectionEventHandler(extensionType.OnCreated, extensionObject).Invoke);
            
            if (extensionType.OnDestroyed == null || !(extensionType.OnDestroyed.MethodInfo != null))
                return;

            extension.DeactivatedEventHandlers = reflectionHandler(extension.DeactivatedEventHandlers,
                new ReflectionEventHandler(extensionType.OnDestroyed, extensionObject).Invoke);
        }

        internal static void AddPlugReflectionHandlers(Plug plug, object extensionObject)
        {
            var reflectionHandler = !plug.reflectionHandlersAdded
                ? new AddReflectionHandler<CompositionEventHandler<CompositionEventArgs>>(InsertReflectionHandler)
                : new AddReflectionHandler<CompositionEventHandler<CompositionEventArgs>>(ReplaceReflectionHandler);

            plug.reflectionHandlersAdded = true;
            var plugType = plug.PlugType;
            
            if (plugType.OnPlugged != null && plugType.OnPlugged.MethodInfo != null)
                plug.PluggedEventHandlers = reflectionHandler(plug.PluggedEventHandlers,
                    new ReflectionEventHandler(plugType.OnPlugged, extensionObject).Invoke);
            
            if (plugType.OnSelected != null && plugType.OnSelected.MethodInfo != null)
                plug.SelectedEventHandlers = reflectionHandler(plug.SelectedEventHandlers,
                    new ReflectionEventHandler(plugType.OnSelected, extensionObject).Invoke);
            
            if (plugType.OnUnplugged != null && plugType.OnUnplugged.MethodInfo != null)
                plug.UnpluggedEventHandlers = reflectionHandler(plug.UnpluggedEventHandlers,
                    new ReflectionEventHandler(plugType.OnUnplugged, extensionObject).Invoke);
            
            if (plugType.OnDeselected == null || !(plugType.OnDeselected.MethodInfo != null))
                return;
            
            plug.DeselectedEventHandlers = reflectionHandler(plug.DeselectedEventHandlers,
                new ReflectionEventHandler(plugType.OnDeselected, extensionObject).Invoke);
        }

        internal static void AddSlotReflectionHandlers(Slot slot, object extensionObject)
        {
            AddReflectionHandler<CompositionEventHandler<SlotEventArgs>> reflectionHandler1;
            AddReflectionHandler<CompositionEventHandler<CompositionEventArgs>> reflectionHandler2;

            if (slot.reflectionHandlersAdded)
            {
                reflectionHandler1 = new AddReflectionHandler<CompositionEventHandler<SlotEventArgs>>(ReplaceReflectionHandler);
                reflectionHandler2 = new AddReflectionHandler<CompositionEventHandler<CompositionEventArgs>>(ReplaceReflectionHandler);
            }
            else
            {
                reflectionHandler1 = new AddReflectionHandler<CompositionEventHandler<SlotEventArgs>>(InsertReflectionHandler);
                reflectionHandler2 = new AddReflectionHandler<CompositionEventHandler<CompositionEventArgs>>(InsertReflectionHandler);
            }

            slot.reflectionHandlersAdded = true;
            var slotType = slot.SlotType;
            
            if (slotType.OnOpened != null && slotType.OnOpened.MethodInfo != null)
                slot.OpenedEventHandlers = reflectionHandler1(slot.OpenedEventHandlers,
                    new ReflectionEventHandler(slotType.OnOpened, extensionObject).Invoke);
            
            if (slotType.OnPlugged != null && slotType.OnPlugged.MethodInfo != null)
                slot.PluggedEventHandlers = reflectionHandler2(slot.PluggedEventHandlers,
                    new ReflectionEventHandler(slotType.OnPlugged, extensionObject).Invoke);
            
            if (slotType.OnSelected != null && slotType.OnSelected.MethodInfo != null)
                slot.SelectedEventHandlers = reflectionHandler2(slot.SelectedEventHandlers,
                    new ReflectionEventHandler(slotType.OnSelected, extensionObject).Invoke);
            
            if (slotType.OnClosed != null && slotType.OnClosed.MethodInfo != null)
                slot.ClosedEventHandlers = reflectionHandler1(slot.ClosedEventHandlers,
                    new ReflectionEventHandler(slotType.OnClosed, extensionObject).Invoke);
            
            if (slotType.OnUnplugged != null && slotType.OnUnplugged.MethodInfo != null)
                slot.UnpluggedEventHandlers = reflectionHandler2(slot.UnpluggedEventHandlers,
                    new ReflectionEventHandler(slotType.OnUnplugged, extensionObject).Invoke);
            
            if (slotType.OnDeselected == null || !(slotType.OnDeselected.MethodInfo != null))
                return;
            
            slot.DeselectedEventHandlers = reflectionHandler2(slot.DeselectedEventHandlers,
                new ReflectionEventHandler(slotType.OnDeselected, extensionObject).Invoke);
        }

        private void RemoveReflectionHandlers(Extension extension)
        {
            RemoveExtensionReflectionHandlers(extension);

            foreach (var plug in extension.Plugs.Where(plug =>
                Qualifier.InternalIsQualified(plug.PlugType)))
                RemovePlugReflectionHandlers(plug);
            
            foreach (var slot in extension.Slots.Where(slot =>
                Qualifier.InternalIsQualified(slot.SlotType)))
                RemoveSlotReflectionHandlers(slot);
        }

        private static void RemoveExtensionReflectionHandlers(Extension extension)
        {
            if (!extension.reflectionHandlersAdded)
                return;
            
            extension.reflectionHandlersAdded = false;
            var extensionType = extension.ExtensionType;
            
            if (extensionType.OnCreated != null && extensionType.OnCreated.MethodInfo != null)
                extension.ActivatedEventHandlers = ArrayEx.RemoveAt(extension.ActivatedEventHandlers, 0);
            
            if (extensionType.OnDestroyed == null || !(extensionType.OnDestroyed.MethodInfo != null))
                return;
            
            extension.DeactivatedEventHandlers = ArrayEx.RemoveAt(extension.DeactivatedEventHandlers, 0);
        }

        private static void RemovePlugReflectionHandlers(Plug plug)
        {
            if (!plug.reflectionHandlersAdded)
                return;

            plug.reflectionHandlersAdded = false;
            var plugType = plug.PlugType;
            
            if (plugType.OnPlugged != null && plugType.OnPlugged.MethodInfo != null)
                plug.PluggedEventHandlers = ArrayEx.RemoveAt(plug.PluggedEventHandlers, 0);
            
            if (plugType.OnSelected != null && plugType.OnSelected.MethodInfo != null)
                plug.SelectedEventHandlers = ArrayEx.RemoveAt(plug.SelectedEventHandlers, 0);
            
            if (plugType.OnUnplugged != null && plugType.OnUnplugged.MethodInfo != null)
                plug.UnpluggedEventHandlers = ArrayEx.RemoveAt(plug.UnpluggedEventHandlers, 0);
            
            if (plugType.OnDeselected == null || !(plugType.OnDeselected.MethodInfo != null))
                return;
            
            plug.DeselectedEventHandlers = ArrayEx.RemoveAt(plug.DeselectedEventHandlers, 0);
        }

        private static void RemoveSlotReflectionHandlers(Slot slot)
        {
            if (!slot.reflectionHandlersAdded)
                return;

            slot.reflectionHandlersAdded = false;
            var slotType = slot.SlotType;

            if (slotType.OnOpened != null && slotType.OnOpened.MethodInfo != null)
                slot.OpenedEventHandlers = ArrayEx.RemoveAt(slot.OpenedEventHandlers, 0);
            
            if (slotType.OnPlugged != null && slotType.OnPlugged.MethodInfo != null)
                slot.PluggedEventHandlers = ArrayEx.RemoveAt(slot.PluggedEventHandlers, 0);
            
            if (slotType.OnSelected != null && slotType.OnSelected.MethodInfo != null)
                slot.SelectedEventHandlers = ArrayEx.RemoveAt(slot.SelectedEventHandlers, 0);
            
            if (slotType.OnClosed != null && slotType.OnClosed.MethodInfo != null)
                slot.ClosedEventHandlers = ArrayEx.RemoveAt(slot.ClosedEventHandlers, 0);
            
            if (slotType.OnUnplugged != null && slotType.OnUnplugged.MethodInfo != null)
                slot.UnpluggedEventHandlers = ArrayEx.RemoveAt(slot.UnpluggedEventHandlers, 0);
            
            if (slotType.OnDeselected == null || !(slotType.OnDeselected.MethodInfo != null))
                return;
            
            slot.DeselectedEventHandlers = ArrayEx.RemoveAt(slot.DeselectedEventHandlers, 0);
        }

        private delegate T[] AddReflectionHandler<T>(T[] eventHandlerList, T eventHandler);
    }
}

